#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdint.h>
#include <time.h>
#include "stack.h"

pthread_t thread[NB_THREADS];
struct timespec start_global, stop_global;
struct timespec thread_start[NB_THREADS], thread_stop[NB_THREADS];

typedef struct {
  int id;
  stack_t *stack;
} thread_info; 

static void* 
push_pop_thread(void* arg) {
  thread_info *args;
  args = (thread_info*) arg;

  clock_gettime(CLOCK_MONOTONIC, &thread_start[args->id]);
  //printf("Thread %d started\n", args->id);

  stackelem_t *elem = NULL;
  elem = stackelem_alloc();
  stackelem_init(elem); 
  stackelem_write(elem, args->id);

  int i;
  for (i=0;i<1000000;i++) {
#if TREIBER_STACK
      stack_push_treiber(args->stack, elem);
      stack_push_treiber(args->stack, elem);
      stack_pop_treiber(args->stack, &elem);
      stack_pop_treiber(args->stack, &elem);
#else
      stack_push_elem_safe(args->stack, elem);
      stack_push_elem_safe(args->stack, elem);
      stack_pop_elem_safe(args->stack, &elem);
      stack_pop_elem_safe(args->stack, &elem);
#endif
  }    //printf("%d\n",i);

  clock_gettime(CLOCK_MONOTONIC, &thread_stop[args->id]);

  return NULL;
}


void stack_thread_test() {
  stack_t *stack = NULL;
  stack = stack_alloc();

#if TREIBER_STACK
  stack_init_treiber(stack);
#else
  stack_init(stack);
#endif
  
  thread_info thread_info[NB_THREADS];

  int i;
  for (i=0;i<NB_THREADS;i++) {
    thread_info[i].stack = stack;
    thread_info[i].id = i;
    pthread_create(&thread[i], NULL, push_pop_thread, (void *) &thread_info[i]); 
  }
  
  // Synchronize threads
  for (i = 0; i < NB_THREADS; i++) {
    pthread_join(thread[i], NULL);
  }
}

stack_t *stack_pool;
stackelem_t *a;
stackelem_t *b;
stackelem_t *c;

static void* 
stack_aba_test_thread(void* arg) {
  thread_info *args;
  args = (thread_info*) arg;

  stackelem_t *elem, *elem2;
  if (args->id == 7) {
    stack_pop_treiber(args->stack, &elem);      
    
    //printf("stack->top value: %d\n", stackelem_read(args->stack->top));
    //pthread_yield();
    printf("stack_pool->top value: %d\n", stack_pool[6].top);
    printf("stack->top value: %d\n", args->stack->top);
    printf("elem: %d\n", elem);
    printf("a: %d\n", a);    
    printf("b: %d\n", b);
    printf("c: %d\n", c);

    if (args->stack->top == stack_pool[6].top) {
      printf("ABA detected\n");
    }
    //print_stack(args->stack->top,0);

  } else if (args->id == 6) {
    printf("Start\n");
    //print_stack(args->stack->top,0);

    stack_pop_treiber(args->stack, &elem);
    stack_pop_treiber(args->stack, &elem2);
    stack_push_treiber(&stack_pool[args->id], elem2);
    stack_push_treiber(args->stack, elem);
    
    //stack_push_treiber(&stack_pool[args->id], elem2);
    //stack_push_treiber(args->stack, elem);
    print_stack(args->stack->top,0);
    printf("End\n");
    //print_stack(stack_pool[args->id].top, 0);
  } else {
    int j;
    for(j=0;j<10000;j++){
      int k = 3;
    }
  }
  return NULL;
}
void
stack_aba_test() {
  stack_t *stack = NULL;
  stack = stack_alloc();
  
  stack_init_treiber(stack);
  
  thread_info thread_info[NB_THREADS];
  

  int j;
  for(j=0;j<100;j++) {
    // Initiera thread-pool
    int i;
    stack_pool = (stack_t *) malloc(NB_THREADS * sizeof(stack_t));
    for (i = 0; i< NB_THREADS; i++) {
      stack_init_treiber(&stack_pool[i]);
    }
    
    // Push first elements
    a = stackelem_alloc();
    b = stackelem_alloc();
    c = stackelem_alloc();
    stackelem_init(c);
    stackelem_write(c, 1);
    stack_push_treiber(stack, c);
    stackelem_init(b);
    stackelem_write(b, 2);
    stack_push_treiber(stack, b);
    stackelem_init(a);
    stackelem_write(a, 3);
    stack_push_treiber(stack, a);
    //print_stack(stack->top, 0);

    for (i=0;i<NB_THREADS;i++) {
      thread_info[i].stack = stack;
      thread_info[i].id = i;
      pthread_create(&thread[i], NULL, stack_aba_test_thread, (void *) &thread_info[i]); 
    }
    
    // Synchronize threads
    for (i = 0; i < NB_THREADS; i++) {
      pthread_join(thread[i], NULL);
    }
    
    //print_stack(stack->top,0);

    //stackelem_free(elem);
    stack_free(stack);
  }
}

void 
simple_stack_test() {
  stack_t *stack = NULL;
  stack = stack_alloc();
  stack_init_treiber(stack);

  stackelem_t *elem = NULL;
  stackelem_t *elem2;// = NULL;
  stackelem_t *elem3;
  elem = stackelem_alloc();
  stackelem_init(elem); 
  //elem2 = stackelem_alloc();
  //stackelem_init(elem2); 
  stackelem_write(elem, 42);

  stack_push_treiber(stack, elem);

  //stackelem_write(elem, 32);
  //stack_push_treiber(stack, elem);
  print_stack(stack->top, 0);
  
  print_stack(stack->top, 0);
  stack_pop_treiber(stack, &elem2);
  //stack_push_treiber(stack, elem2);
  //print_stack(stack->top, 0);
  
  printf("%d\n", elem);
  printf("%d\n", elem2);

  //print_stack(elem,0);
  
  //stack_free(stack);
}

int main() {
  //simple_stack_test();
  stack_aba_test();

  clock_gettime(CLOCK_MONOTONIC, &start_global); 
  //stack_thread_test();
  clock_gettime(CLOCK_MONOTONIC, &stop_global); 
  
  int i, try = 0;
  for (i = 0; i < NB_THREADS; i++)
    {
      printf("%i %i %i %li %i %li %i %li %i %li %i;\n",
	     NB_THREADS, i + 1, (int) start_global.tv_sec,
	     start_global.tv_nsec, (int) stop_global.tv_sec, 
	     stop_global.tv_nsec,  (int) thread_start[i].tv_sec, 
	     thread_start[i].tv_nsec, (int) thread_stop[i].tv_sec,
	     thread_stop[i].tv_nsec, try);
    }

  int gl_stop =  (int) stop_global.tv_sec * 1000000 + 
    (int) stop_global.tv_nsec / 1000;
  int gl_start = (int) start_global.tv_sec * 1000000 + 
    (int) start_global.tv_nsec / 1000;

  printf("Execution time: %f\n", ((float)gl_stop-(float)gl_start)/1000000.0);
  return 0;
}
